package qc.module.platform.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import qc.common.core.enums.ClientTypeEnum;
import qc.common.core.enums.QCAuthValidateResultEnum;
import qc.common.core.exception.QCPromptException;
import qc.common.core.exception.QcTokenInvalidException;
import qc.common.core.http.ResponseStatusEnum;
import qc.common.core.unify.QCUnifyReturnValue;
import qc.common.core.utils.DateUtil;
import qc.module.platform.dto.log.UserLoginLogDto;
import qc.module.platform.dto.log.UserLoginLogQueryConditionDto;
import qc.module.platform.entity.QcUserloginLogs;
import qc.module.platform.mapper.UserloginLogsMapper;
import qc.module.platform.repository.QcUserloginLogsRepository;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * UserLoginLogService
 *
 * @author QuCheng Tech
 * @create 2023/1/29
 */
@Service
public class UserLoginLogService {
    private QcUserloginLogsRepository repository;

    @Autowired
    public void setQcUserloginLogsRepository(QcUserloginLogsRepository repository) {
        this.repository = repository;
    }

    /**
     * 添加登录日志记录
     *
     * @param log 登录日志
     * @return java.lang.String
     * @author QuCheng Tech
     * @date 2023/1/29
     */
    public String addLog(QcUserloginLogs log) {
        //在此对数据长度有效性进行处理，避免出现部分列长度超过数据库表长度导致无法保存
        if (StringUtils.isNotBlank(log.getClientinfo()) && log.getClientinfo().length() > 255)
            log.setClientinfo(log.getClientinfo().substring(0, 254));
        if (StringUtils.isNotBlank(log.getIp()) && log.getIp().length() > 20)
            log.setIp(log.getIp().substring(0, 19));
        if (StringUtils.isNotBlank(log.getMsg()) && log.getMsg().length() > 255)
            log.setMsg(log.getMsg().substring(0, 254));

        if (repository.insert(log) < 0x1)
            return QCUnifyReturnValue.Warn("保存失败");

        return QCUnifyReturnValue.Success();
    }

    /**
     * 根据token获取用户登录记录信息，如果token为空抛出异常，如果要token有效验证不通过抛出异常
     *
     * @param token        用户登录时得到的token
     * @param isTokenValid token是否有效判断，如为true进行token有效性判断
     * @return 返回结果必须为用户登录日志，否则就是抛出token异常
     * @author QuCheng Tech
     * @date 2023/1/11
     */
    public QcUserloginLogs getUserLoginLogByToken(String token, boolean isTokenValid) throws QcTokenInvalidException {
        //如输入的token为空，抛出异常
        if (StringUtils.isBlank(token))
            throw new QcTokenInvalidException(ResponseStatusEnum.AUTH_NO_TOKEN.getValue().toString());

        LambdaQueryWrapper<QcUserloginLogs> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(QcUserloginLogs::getToken, token);

        wrapper.orderByDesc(QcUserloginLogs::getTm);

        QcUserloginLogs latestLog = repository.selectOne(wrapper);
        //如果根据token得到的登录记录为空，表示token无效
        if (latestLog == null)
            throw new QcTokenInvalidException(ResponseStatusEnum.AUTH_INVALID_TOKEN.getValue().toString());

        if (isTokenValid) {
            //判断token是否有效
            //先判断登录结果和UID，再判断token是否过期
            if (latestLog.getStatus() == QCAuthValidateResultEnum.SUCCESS) {
                //如果登录日志中的用户ID为空，抛出异常--token无效
                if (latestLog.getUid() == null)
                    throw new QcTokenInvalidException(ResponseStatusEnum.AUTH_INVALID_TOKEN.getValue().toString());

                //判断token是否过期
                if (latestLog.getExpiredtm() != null && latestLog.getExpiredtm().before(DateUtil.getNowDate()))
                    throw new QcTokenInvalidException(ResponseStatusEnum.AUTH_EXPIRED_TOKEN.getValue().toString());
                //token过期时间为空，或者过期时间大于当前时间，token有效，返回登录日志记录
                //return logs;
            }
        } else {
            //不用判断token是否有效，直接返回即可，登录失败时记录中没有UID即为null
            //return logs;
        }
        return latestLog;
    }

    /**
     * 查询登陆日志
     *
     * @param dto UserLoginLogQueryConditionDto
     * @return 用户登陆日志集合
     * @author QcCheng Tech
     * @since 2023/2/8
     */
    public List<UserLoginLogDto> query(UserLoginLogQueryConditionDto dto) throws Exception {
        if (dto == null)
            throw new QCPromptException("传入对象为空");
        if (StringUtils.isBlank(dto.getBegintm()))
            throw new QCPromptException("开始时间不能为空");
        if (StringUtils.isBlank(dto.getEndtm()))
            throw new QCPromptException("结束时间不能为空");
        if (DateUtil.parseDate(dto.getBegintm()).after(DateUtil.parseDate(dto.getEndtm())))
            throw new QCPromptException("开始时间大于结束时间");

        LambdaQueryWrapper<QcUserloginLogs> wrapper = new LambdaQueryWrapper<>();

        //登录时间
        wrapper.between(QcUserloginLogs::getTm, dto.getBegintm(), dto.getEndtm());
        //查询条件中的登录结果状态
        if (StringUtils.isNotBlank(dto.getStatus())) {
            if (StringUtils.equalsIgnoreCase(dto.getStatus(), "1")) {
                //只查询登录结果为成功的
                wrapper.eq(QcUserloginLogs::getStatus, QCAuthValidateResultEnum.SUCCESS);
            } else if (StringUtils.equalsIgnoreCase(dto.getStatus(), "0")) {
                //只查询登录结果为失败的
                wrapper.ne(QcUserloginLogs::getStatus, QCAuthValidateResultEnum.SUCCESS);
            }
            //其他情况下查询所有登录记录
        }
        //查询条件中指定了项目编码，精确匹配项目编码
        if (StringUtils.isNotBlank(dto.getProjectcode()))
            wrapper.eq(QcUserloginLogs::getProject, dto.getProjectcode());
        //查询条件中指定了用户ID，精确匹配用户ID
        if (StringUtils.isNotBlank(dto.getUid()))
            wrapper.eq(QcUserloginLogs::getUid, dto.getUid());
        //查询条件中指定了token，精确匹配token
        if (StringUtils.isNotBlank(dto.getToken()))
            wrapper.eq(QcUserloginLogs::getToken, dto.getToken());
        //查询条件中指定了关键字，在登录名、客户端信息、登录结果中模糊匹配
        if (StringUtils.isNotBlank(dto.getWords()))
            wrapper.and(k -> k.like(QcUserloginLogs::getUcode, dto.getWords())
                    .or().like(QcUserloginLogs::getClientinfo, dto.getWords())
                    .or().like(QcUserloginLogs::getMsg, dto.getWords()));

        //结果降序排序
        wrapper.orderByDesc(QcUserloginLogs::getTm);

        List<QcUserloginLogs> ens = repository.selectList(wrapper);

        if (ens.size() > 0x0) {
            List<UserLoginLogDto> result = new ArrayList<>();
            for (QcUserloginLogs en : ens) {
                result.add(UserloginLogsMapper.MAPPER.entityToDto(en));
            }
            return result;
        }

        return null;
    }

    /**
     * 根据id获取登录日志详情
     *
     * @param id
     * @return 登录日志详情
     * @author QcCheng Tech
     * @since 2023/2/8
     */
    public UserLoginLogDto get(String id) throws Exception {
        if (StringUtils.isBlank(id))
            throw new QCPromptException("日志id不能为空");

        LambdaQueryWrapper<QcUserloginLogs> wrapper = new LambdaQueryWrapper<>();

        wrapper.eq(QcUserloginLogs::getId, id);
        QcUserloginLogs log = repository.selectOne(wrapper);

        if (log == null)
            throw new QCPromptException("日志不存在");

        return UserloginLogsMapper.MAPPER.entityToDto(log);
    }

    /**
     * 根据用户id查询用户登录过的token
     *
     * @param uid 用户id
     * @return 用户登录过的token列表
     * @throws QCPromptException
     * @author QuCheng Tech
     * @since 2023/7/7
     */
    public List<String> getTokensByUid(String uid) throws QCPromptException {
        if (StringUtils.isBlank(uid))
            throw new QCPromptException("用户id不能为空");

        LambdaQueryWrapper<QcUserloginLogs> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(QcUserloginLogs::getToken).eq(QcUserloginLogs::getUid, uid);
        List<QcUserloginLogs> logList = repository.selectList(wrapper);

        List<String> tokens = new ArrayList<>();

        if (logList.size() > 0x0) {
            logList.forEach(log -> tokens.add(log.getToken()));
        }
        return tokens;
    }

    /***
     * 设置指定用户指定客户端类型的历史token为过期
     *
     * @param userId 用户ID
     * @param clientType 客户端类型
     * @param beforeTime 小于的时间，在此时间前的登录记录
     * @param excludeToken 需要排除在外的token，可以为空
     * @return void
     * @author QuCheng Tech
     * @since 2023/11/13
     */
    public void setTokenExpired(Integer userId, ClientTypeEnum clientType, Date beforeTime, String excludeToken) {
        if (userId != null) {
            LambdaUpdateWrapper<QcUserloginLogs> wrapper = new LambdaUpdateWrapper<>();
            //设置用户登录日志中的token过期时间为本次登录时间
            wrapper.set(QcUserloginLogs::getExpiredtm, beforeTime);

            //修改token过期的条件：指定用户ID、指定客户端类型、登录时间小于本次登录时间、token过期时间为空或大于本次登录时间
            wrapper.eq(QcUserloginLogs::getUid, userId);
            wrapper.eq(QcUserloginLogs::getClienttype, clientType);
            wrapper.lt(QcUserloginLogs::getTm, beforeTime);
            //如果本次登录成功，需要将本次的token排除在外
            if (!StringUtils.isBlank(excludeToken))
                wrapper.ne(QcUserloginLogs::getToken, excludeToken);
            //过期时间为空或大于当前时间
            wrapper.and(p -> p.isNull(QcUserloginLogs::getExpiredtm).or().ge(QcUserloginLogs::getExpiredtm, beforeTime));

            repository.update(null, wrapper);
        }
    }

    /***
     * 设置指定的token为过期
     *
     * @param token 要设置为过期的token
     * @return void
     * @author QuCheng Tech
     * @since 2025/1/10
     */
    public void setTokenExpired(String token) {
        if (StringUtils.isNotBlank(token)) {
            Date now = DateUtil.getNowDate();
            LambdaUpdateWrapper<QcUserloginLogs> wrapper = new LambdaUpdateWrapper<>();
            //设置用户登录日志中的token过期时间为当前时间
            wrapper.set(QcUserloginLogs::getExpiredtm, now);

            wrapper.eq(QcUserloginLogs::getToken, token);
            //过期时间为空或大于当前时间
            wrapper.and(p -> p.isNull(QcUserloginLogs::getExpiredtm).or().gt(QcUserloginLogs::getExpiredtm, now));

            repository.update(null, wrapper);
        }
    }

    /***
     * 获取指定IP在指定时间段内登录失败的记录，按时间降序返回
     *
     * @param ip IP地址
     * @param beginTime 起始时间
     * @param endTime 截止时间
     * @return long
     * @author QuCheng Tech
     * @since 2025/1/13
     */
    public List<QcUserloginLogs> getLoginFailTimes(String ip, Date beginTime, Date endTime) {
        LambdaQueryWrapper<QcUserloginLogs> wrapper = new LambdaQueryWrapper<>();

        wrapper.between(QcUserloginLogs::getTm, beginTime, endTime);
        wrapper.eq(QcUserloginLogs::getIp, ip);
        wrapper.ne(QcUserloginLogs::getStatus, QCAuthValidateResultEnum.SUCCESS);

        wrapper.orderByDesc(QcUserloginLogs::getTm);

        return repository.selectList(wrapper);
    }

    /***
     * 获取指定用户ID在指定时间段内的登录失败记录，按时间降序返回
     *
     * @param userId 用户ID
     * @param beginTime 起始时间
     * @param endTime 截止时间
     * @return java.util.List<qc.module.platform.entity.QcUserloginLogs>
     * @author QuCheng Tech
     * @since 2025/2/26
     */
    public List<QcUserloginLogs> getLoginFailLogsByUserId(int userId, Date beginTime, Date endTime) {
        LambdaQueryWrapper<QcUserloginLogs> wrapper = new LambdaQueryWrapper<>();

        wrapper.between(QcUserloginLogs::getTm, beginTime, endTime);
        wrapper.eq(QcUserloginLogs::getUid, userId);
        wrapper.ne(QcUserloginLogs::getStatus, QCAuthValidateResultEnum.SUCCESS);

        wrapper.orderByDesc(QcUserloginLogs::getTm);

        return repository.selectList(wrapper);
    }
}

